#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""  
@Project : audit
@File : Apache_rules.py
@Author : 陳
@Time : 2025/11/3 11:28  
@脚本说明 : 
"""

import subprocess
import re
from typing import Dict, List, Any
import logging

logger = logging.getLogger(__name__)


class ApacheXAMPPSecurityRules:
    """XAMPP环境下Apache安全加固规则类"""

    @staticmethod
    def run_cmd_simple(command: str) -> str:
        """简化的命令执行，处理Linux编码问题"""
        try:
            result = subprocess.run(
                command,
                shell=True,
                capture_output=True,
                timeout=30,
                text=True
            )
            return result.stdout.strip() or result.stderr.strip()
        except Exception as e:
            return f"Error: {str(e)}"

    @staticmethod
    def find_xampp_apache_config() -> str:
        """查找XAMPP环境下Apache主配置文件路径"""
        # XAMPP常见安装路径
        common_paths = [
            "/opt/lampp/etc/httpd.conf",
            "/opt/xampp/etc/httpd.conf",
            "/usr/local/xampp/etc/httpd.conf"
        ]
        for path in common_paths:
            if ApacheXAMPPSecurityRules.run_cmd_simple(f"test -f {path} && echo 1 || echo 0") == "1":
                return path

        # 尝试通过XAMPP控制脚本查找
        xampp_control = ApacheXAMPPSecurityRules.run_cmd_simple("which xampp 2>/dev/null || which lampp 2>/dev/null")
        if xampp_control:
            install_dir = re.search(r'^(.+)/bin/(xampp|lampp)$', xampp_control).group(1)
            candidate = f"{install_dir}/etc/httpd.conf"
            if ApacheXAMPPSecurityRules.run_cmd_simple(f"test -f {candidate} && echo 1 || echo 0") == "1":
                return candidate

        return ""

    @staticmethod
    def check_non_root_user() -> Dict[str, Any]:
        """1. 检查是否使用非root用户启动Apache"""
        print("[Apache规则1] 检查非root用户启动配置...")
        status_details = []
        issues = []
        config_path = ApacheXAMPPSecurityRules.find_xampp_apache_config()
        has_problem = False

        if not config_path:
            issues.append("未找到XAMPP Apache配置文件")
            status_details.append("✗ 配置文件: 未找到httpd.conf")
            return {
                "rule_name": "非root用户启动检查",
                "status": "未通过",
                "config_status": {"config_exists": False},
                "status_details": status_details,
                "recommendation": "请先确认XAMPP Apache配置文件位置"
            }

        # 检查User和Group配置
        content = ApacheXAMPPSecurityRules.run_cmd_simple(f"cat {config_path}")
        user_match = re.search(r'^User\s+(\w+)$', content, re.MULTILINE)
        group_match = re.search(r'^Group\s+(\w+)$', content, re.MULTILINE)

        run_user = user_match.group(1) if user_match else "未知"
        run_group = group_match.group(1) if group_match else "未知"

        # 检查实际运行用户
        ps_output = ApacheXAMPPSecurityRules.run_cmd_simple(
            # 查找httpd进程中用户非root的进程，优先取第一个（工作进程）
            "ps aux | grep 'httpd' | grep -v 'grep' | grep -v 'root' | head -n 1 | awk '{print $1}'"
        )
        actual_user = ps_output.strip()

        if user_match and run_user == "root":
            has_problem = True
            issues.append(f"配置文件中使用root用户运行Apache")
            status_details.append(f"✗ 配置用户: {run_user}（高风险，不应使用root）")
        elif user_match:
            status_details.append(f"✓ 配置用户: {run_user}（非root用户）")

        if actual_user == "root":
            has_problem = True
            issues.append(f"实际运行用户为root")
            status_details.append(f"✗ 运行用户: {actual_user}（高风险，不应使用root）")
        elif actual_user:
            status_details.append(f"✓ 运行用户: {actual_user}（非root用户）")

        return {
            "rule_name": "非root用户启动检查",
            "status": "通过" if not has_problem else "未通过",
            "config_status": {
                "config_exists": bool(config_path),
                "configured_user": run_user,
                "actual_user": actual_user,
                "is_root": actual_user == "root"
            },
            "status_details": status_details,
            "recommendation": "已使用非root用户运行Apache" if not has_problem else
            "需在httpd.conf中修改User和Group为非root用户（如daemon或apache）并重启服务"
        }

    @staticmethod
    def check_strong_password() -> Dict[str, Any]:
        """2. 检查Apache账户强密码配置"""
        print("[Apache规则2] 检查账户强密码配置...")
        status_details = []
        issues = []

        # 检查XAMPP控制面板密码设置
        xampp_security = ApacheXAMPPSecurityRules.run_cmd_simple(
            "grep '^$cfg\[' /opt/lampp/phpmyadmin/config.inc.php 2>/dev/null | grep -i 'password'"
        )

        # 检查系统用户密码复杂度（针对Apache运行用户）
        config_path = ApacheXAMPPSecurityRules.find_xampp_apache_config()
        run_user = "daemon"
        if config_path:
            content = ApacheXAMPPSecurityRules.run_cmd_simple(f"cat {config_path}")
            user_match = re.search(r'^User\s+(\w+)$', content, re.MULTILINE)
            if user_match:
                run_user = user_match.group(1)

        # 检查用户是否有密码
        passwd_check = ApacheXAMPPSecurityRules.run_cmd_simple(
            f"grep '^{run_user}:' /etc/shadow | cut -d: -f2"
        )
        has_password = passwd_check not in ("", "!!", "*")

        # 检查密码复杂度（简单判断）
        passwd_strength = "弱"
        if has_password:
            # 实际环境中应使用更复杂的密码强度检测
            passwd_strength = "强" if len(passwd_check) > 8 else "弱"

        if not xampp_security:
            issues.append("未设置XAMPP控制面板密码")
            status_details.append("✗ 控制面板: 未配置密码（高风险）")
        else:
            status_details.append("✓ 控制面板: 已配置密码")

        if not has_password:
            issues.append(f"Apache运行用户{run_user}未设置密码")
            status_details.append(f"✗ 账户密码: {run_user}无密码（高风险）")
        elif passwd_strength == "弱":
            issues.append(f"Apache运行用户{run_user}密码强度不足")
            status_details.append(f"✗ 密码强度: {run_user}密码强度较弱")
        else:
            status_details.append(f"✓ 密码强度: {run_user}密码强度符合要求")

        return {
            "rule_name": "账户强密码检查",
            "status": "通过" if not issues else "未通过",
            "config_status": {
                "control_panel_secured": bool(xampp_security),
                "user_has_password": has_password,
                "password_strength": passwd_strength
            },
            "status_details": status_details,
            "recommendation": "账户密码配置符合安全要求" if not issues else
            "需设置XAMPP控制面板密码并为运行用户配置强密码（长度≥8位，包含大小写字母、数字和特殊字符）"
        }

    @staticmethod
    def check_minimal_permissions() -> Dict[str, Any]:
        """3. 检查最小化权限配置"""
        print("[Apache规则3] 检查最小化权限配置...")
        status_details = []
        issues = []
        config_path = ApacheXAMPPSecurityRules.find_xampp_apache_config()

        # 获取Apache运行用户
        run_user = "daemon"
        if config_path:
            content = ApacheXAMPPSecurityRules.run_cmd_simple(f"cat {config_path}")
            user_match = re.search(r'^User\s+(\w+)$', content, re.MULTILINE)
            if user_match:
                run_user = user_match.group(1)

        # 检查网站根目录
        doc_root = "/opt/lampp/htdocs"  # XAMPP默认网站根目录
        if config_path:
            content = ApacheXAMPPSecurityRules.run_cmd_simple(f"cat {config_path}")
            root_match = re.search(r'^DocumentRoot\s+"([^"]+)"', content, re.MULTILINE)
            if root_match:
                doc_root = root_match.group(1)

        # 检查目录是否存在
        if ApacheXAMPPSecurityRules.run_cmd_simple(f"test -d {doc_root} && echo 1 || echo 0") == "1":
            # 检查目录权限
            perm_check = ApacheXAMPPSecurityRules.run_cmd_simple(f"stat -c '%a %U' {doc_root}")
            permissions, owner = perm_check.split() if ' ' in perm_check else ("", "")

            if permissions and int(permissions) > 755:
                issues.append(f"网站根目录权限过高({permissions})")
                status_details.append(f"✗ 目录权限: {doc_root} 权限为{permissions}（建议≤755）")
            else:
                status_details.append(f"✓ 目录权限: {doc_root} 权限为{permissions}")

            if owner != run_user:
                issues.append(f"网站根目录所有者为{owner}，与运行用户{run_user}不一致")
                status_details.append(f"✗ 目录所有者: {doc_root} 所有者为{owner}（建议改为{run_user}）")
            else:
                status_details.append(f"✓ 目录所有者: {doc_root} 所有者为{owner}")
        else:
            issues.append(f"网站根目录{doc_root}不存在")
            status_details.append(f"✗ 目录检查: {doc_root} 不存在")

        # 检查配置文件权限
        if config_path:
            perm_check = ApacheXAMPPSecurityRules.run_cmd_simple(f"stat -c '%a' {config_path}")
            if perm_check and int(perm_check) > 644:
                issues.append(f"配置文件权限过高({perm_check})")
                status_details.append(f"✗ 配置文件权限: {config_path} 权限为{perm_check}（建议≤644）")
            else:
                status_details.append(f"✓ 配置文件权限: {config_path} 权限为{perm_check}")

        return {
            "rule_name": "最小化权限检查",
            "status": "通过" if not issues else "未通过",
            "config_status": {
                "run_user": run_user,
                "doc_root": doc_root,
                "doc_root_permissions": permissions if 'permissions' in locals() else "",
                "config_permissions": perm_check if 'perm_check' in locals() else ""
            },
            "status_details": status_details,
            "recommendation": "权限配置符合最小化原则" if not issues else f"需修复: {'; '.join(issues)}"
        }

    @staticmethod
    def check_log_audit() -> Dict[str, Any]:
        """4. 检查日志审计配置"""
        print("[Apache规则4] 检查日志审计配置...")
        status_details = []
        issues = []
        config_path = ApacheXAMPPSecurityRules.find_xampp_apache_config()
        has_access_log = False
        has_error_log = False

        if not config_path:
            issues.append("未找到Apache配置文件")
            status_details.append("✗ 配置文件: 未找到httpd.conf")
            return {
                "rule_name": "日志审计检查",
                "status": "未通过",
                "config_status": {"config_exists": False},
                "status_details": status_details,
                "recommendation": "请先确认Apache配置文件位置"
            }

        # 检查日志配置
        content = ApacheXAMPPSecurityRules.run_cmd_simple(f"cat {config_path}")
        access_log_match = re.search(r'^CustomLog\s+"([^"]+)"', content, re.MULTILINE)
        error_log_match = re.search(r'^ErrorLog\s+"([^"]+)"', content, re.MULTILINE)

        if access_log_match:
            has_access_log = True
            access_log_path = access_log_match.group(1)
            log_exists = ApacheXAMPPSecurityRules.run_cmd_simple(
                f"test -f {access_log_path} && echo 1 || echo 0") == "1"
            status_details.append(f"✓ 访问日志: 配置路径 {access_log_path}（{'存在' if log_exists else '不存在'}）")
        else:
            issues.append("未配置访问日志")
            status_details.append("✗ 访问日志: 未配置")

        if error_log_match:
            has_error_log = True
            error_log_path = error_log_match.group(1)
            log_exists = ApacheXAMPPSecurityRules.run_cmd_simple(f"test -f {error_log_path} && echo 1 || echo 0") == "1"
            status_details.append(f"✓ 错误日志: 配置路径 {error_log_path}（{'存在' if log_exists else '不存在'}）")
        else:
            issues.append("未配置错误日志")
            status_details.append("✗ 错误日志: 未配置")

        # 检查日志轮转
        logrotate_check = ApacheXAMPPSecurityRules.run_cmd_simple(
            "grep 'apache' /etc/logrotate.d/ 2>/dev/null | grep -v '#'")
        if logrotate_check:
            status_details.append("✓ 日志轮转: 已配置")
        else:
            issues.append("未配置日志轮转策略")
            status_details.append("✗ 日志轮转: 未配置（可能导致日志文件过大）")

        return {
            "rule_name": "日志审计检查",
            "status": "通过" if not issues else "未通过",
            "config_status": {
                "access_log_enabled": has_access_log,
                "error_log_enabled": has_error_log,
                "logrotate_enabled": bool(logrotate_check)
            },
            "status_details": status_details,
            "recommendation": "日志审计配置完整" if not issues else f"需修复: {'; '.join(issues)}"
        }

    @staticmethod
    def check_data_backup() -> Dict[str, Any]:
        """5. 检查重要数据备份策略"""
        print("[Apache规则5] 检查数据备份策略...")
        status_details = []
        issues = []

        # XAMPP重要目录
        important_dirs = [
            "/opt/lampp/htdocs",  # 网站根目录
            "/opt/lampp/etc",  # 配置文件目录
            "/opt/lampp/phpmyadmin",  # phpMyAdmin目录
            "/opt/lampp/mysql/data"  # 数据库数据目录
        ]

        # 检查备份文件或目录
        backup_signs = []
        for dir_path in important_dirs:
            if ApacheXAMPPSecurityRules.run_cmd_simple(f"test -d {dir_path} && echo 1 || echo 0") == "1":
                # 检查最近7天的备份
                backups = ApacheXAMPPSecurityRules.run_cmd_simple(
                    f"find {dir_path} ../$(basename {dir_path})_backup* -maxdepth 0 -type d -mtime -7 2>/dev/null"
                )
                if backups:
                    backup_signs.extend(backups.splitlines())

        # 检查备份脚本
        backup_scripts = ApacheXAMPPSecurityRules.run_cmd_simple(
            "grep -r 'lampp' /etc/cron* /var/spool/cron 2>/dev/null | grep -E 'tar|cp|rsync|mysqldump'"
        )

        if backup_signs or backup_scripts:
            status_details.append("✓ 备份状态: 存在近期备份或备份脚本")
            if backup_signs:
                status_details.append(f"  最近备份: {backup_signs[0]}")
            if backup_scripts:
                status_details.append(f"  备份脚本: 存在定时任务配置")
        else:
            issues.append("未发现近期备份或自动备份策略")
            status_details.append("✗ 备份状态: 无有效备份机制")

        return {
            "rule_name": "重要数据备份检查",
            "status": "通过" if not issues else "未通过",
            "config_status": {
                "has_recent_backup": bool(backup_signs),
                "has_backup_script": bool(backup_scripts)
            },
            "status_details": status_details,
            "recommendation": "备份策略配置完善" if not issues else "建议配置定时备份网站目录、配置文件和数据库"
        }

    @staticmethod
    def check_maintenance_patches() -> Dict[str, Any]:
        """6. 检查定期维护和补丁策略"""
        print("[Apache规则6] 检查维护和补丁策略...")
        status_details = []
        issues = []

        # 检查当前Apache版本
        version_output = ApacheXAMPPSecurityRules.run_cmd_simple("/opt/lampp/bin/httpd -v 2>&1")
        version_match = re.search(r'Apache/(\d+\.\d+\.\d+)', version_output)
        current_version = version_match.group(1) if version_match else "未知"
        status_details.append(f"✓ 当前版本: {current_version}")

        # 检查XAMPP版本
        xampp_version = ApacheXAMPPSecurityRules.run_cmd_simple("/opt/lampp/lampp version 2>/dev/null")
        if xampp_version:
            status_details.append(f"✓ XAMPP版本: {xampp_version}")
        else:
            status_details.append("✓ XAMPP版本: 未知")

        # 检查是否有可用更新
        update_check = ApacheXAMPPSecurityRules.run_cmd_simple(
            "apt list --upgradable apache2 2>/dev/null | grep -v 'Listing...' || yum check-update httpd 2>/dev/null | grep httpd"
        )
        if update_check:
            issues.append("存在可用的Apache更新")
            status_details.append("✗ 版本更新: 发现可用更新")
        else:
            status_details.append("✓ 版本更新: 当前为最新版本")

        # 检查自动更新配置
        auto_update = ApacheXAMPPSecurityRules.run_cmd_simple(
            "grep -r 'apache2\\|httpd' /etc/apt/apt.conf.d/ /etc/cron.daily/ 2>/dev/null | grep -i 'upgrade'"
        )
        if auto_update:
            status_details.append("✓ 自动更新: 已配置")
        else:
            issues.append("未配置自动更新策略")
            status_details.append("✗ 自动更新: 未配置定期更新")

        return {
            "rule_name": "维护和补丁策略检查",
            "status": "通过" if not issues else "未通过",
            "config_status": {
                "current_version": current_version,
                "has_updates": bool(update_check),
                "auto_update_enabled": bool(auto_update)
            },
            "status_details": status_details,
            "recommendation": "维护策略完善" if not issues else f"需修复: {'; '.join(issues)}"
        }

    @staticmethod
    def check_version_hiding() -> Dict[str, Any]:
        """8. 检查是否隐藏Apache版本信息"""
        print("[Apache规则8] 检查版本信息隐藏配置...")
        status_details = []
        issues = []
        config_path = ApacheXAMPPSecurityRules.find_xampp_apache_config()
        has_problem = False

        if not config_path:
            issues.append("未找到Apache配置文件")
            status_details.append("✗ 配置文件: 未找到httpd.conf")
            return {
                "rule_name": "隐藏版本信息检查",
                "status": "未通过",
                "config_status": {"config_exists": False},
                "status_details": status_details,
                "recommendation": "请先确认Apache配置文件位置"
            }

        # 检查ServerTokens和ServerSignature配置
        content = ApacheXAMPPSecurityRules.run_cmd_simple(f"cat {config_path}")
        server_tokens = re.search(r'ServerTokens\s+(\w+)', content)
        server_signature = re.search(r'ServerSignature\s+(\w+)', content)

        # 理想配置: ServerTokens Prod, ServerSignature Off
        tokens_secure = server_tokens and server_tokens.group(1) == "Prod"
        signature_secure = server_signature and server_signature.group(1) == "Off"

        # 检查响应头（实际生效验证）
        port = re.search(r'Listen\s+(\d+)', content)
        test_port = port.group(1) if port else "80"
        header_check = ApacheXAMPPSecurityRules.run_cmd_simple(
            f"curl -I http://localhost:{test_port} 2>/dev/null | grep 'Server: Apache'"
        )
        version_exposed = re.search(r'Apache/(\d+\.\d+\.\d+)', header_check) is not None

        if tokens_secure and signature_secure and not version_exposed:
            status_details.append("✓ 版本信息: 已隐藏")
        else:
            has_problem = True
            if not tokens_secure:
                issues.append("未配置ServerTokens Prod")
                status_details.append("✗ 配置检查: 缺少ServerTokens Prod")
            if not signature_secure:
                issues.append("未配置ServerSignature Off")
                status_details.append("✗ 配置检查: 缺少ServerSignature Off")
            if version_exposed:
                issues.append("响应头仍暴露版本信息")
                status_details.append("✗ 响应头检查: 版本信息可见")

        return {
            "rule_name": "隐藏版本信息检查",
            "status": "通过" if not has_problem else "未通过",
            "config_status": {
                "server_tokens_prod": tokens_secure,
                "server_signature_off": signature_secure,
                "version_exposed": version_exposed
            },
            "status_details": status_details,
            "recommendation": "版本信息隐藏配置正确" if not has_problem else
            "需在httpd.conf中添加ServerTokens Prod和ServerSignature Off并重启Apache"
        }

    @staticmethod
    def get_all_rules() -> List[callable]:
        """获取所有Apache审计规则的函数列表"""
        return [
            ApacheXAMPPSecurityRules.check_non_root_user,
            ApacheXAMPPSecurityRules.check_strong_password,
            ApacheXAMPPSecurityRules.check_minimal_permissions,
            ApacheXAMPPSecurityRules.check_log_audit,
            ApacheXAMPPSecurityRules.check_data_backup,
            ApacheXAMPPSecurityRules.check_maintenance_patches,
            ApacheXAMPPSecurityRules.check_version_hiding
        ]